package com.octopus.kernal.util.generate;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;

import javax.persistence.CascadeType;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Transient;

import org.apache.commons.beanutils.BeanUtils;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Index;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
import com.octopus.kernal.controller.AbstractSearchCommand;
import com.octopus.kernal.controller.AbstractValidator;
import com.octopus.kernal.controller.ControllerInterface;
import com.octopus.kernal.dao.GenericHibernateDao;
import com.octopus.kernal.model.BaseStruct;
import com.octopus.kernal.service.AbstractEntityService;
import com.octopus.kernal.ui.builder.DetailTableBuilder;
import com.octopus.kernal.ui.builder.QueryTableBuilder;
import com.octopus.kernal.ui.builder.SearchTableBuilder;
import com.octopus.kernal.util.xml.*;
import com.sun.codemodel.JAnnotationArrayMember;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JArray;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JDocComment;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JTryBlock;
import com.sun.codemodel.JType;
import javax.servlet.http.HttpServletRequest;

public class JavaCodeGenerator {
	
	@SuppressWarnings("unused")
	private static Log log = LogFactory.getLog(JavaCodeGenerator.class);
	
	@SuppressWarnings("unused")
	private BusinessModel businessModel;
	
	private String basePackage;
	
	public  JavaCodeGenerator(String basePackage, BusinessModel businessModel){
		this.businessModel = businessModel;
		this.basePackage = basePackage;
	}
	
	/**
	 * Generate persist model {Entity}Struct.java files
	 * @param entity
	 * @throws JClassAlreadyExistsException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public void generatePersistModel(Entity entity) throws JClassAlreadyExistsException, IOException, ClassNotFoundException{
		String packageGenerated = basePackage + ".generated.model";
		String packageCustomized = basePackage + ".customized.model";
		JCodeModel codeModel = new JCodeModel();
		// Class
		JPackage jp = codeModel._package(packageGenerated);
		JDefinedClass jc = jp._class(entity.getName() + "Struct");
		jc._extends(BaseStruct.class);
		JDocComment jDocComment = jc.javadoc();
        jDocComment.add("Generated class based on BusinessModel.xml");
		JAnnotationUse annotation = jc.annotate(SuppressWarnings.class);
		annotation.param("value", "serial");
		if(entity.getPersist()){
			jc.annotate(javax.persistence.Entity.class);
			jc.annotate(javax.persistence.Table.class).param("name", entity.getName());
		}
		JMethod method = null;
		JBlock body = null;
		JInvocation invocation = null;
		// ID getter/setter
		method = jc.method(JMod.PUBLIC, codeModel._ref(Long.class), "getId");
		if(entity.getPersist()){
			method.annotate(Id.class);
			annotation = method.annotate(GeneratedValue.class);
			annotation.param("strategy", GenerationType.AUTO);
		}
		body = method.body();
		body._return(JExpr.ref("id"));
		method = jc.method(JMod.PUBLIC, codeModel.VOID, "setId");
		method.param(codeModel._ref(Long.class), "id");
		body = method.body();
		body.directStatement("this.id = id;");
		invocation = body.invoke("setAttributeValue");
		invocation.arg("id");
		invocation.arg(JExpr.ref("id"));
		// Fields and getter/setter
		if(entity.getFields() != null){
			JMethod methodClear = jc.method(JMod.PUBLIC, codeModel.VOID, "clear");
			methodClear.annotate(Override.class);
			for(Field field : entity.getFields()){
				Column column = field.getColumn();
				if(column == null){
					continue;
				}
				JType type = null;
				boolean unchecked = false;
				if(column.getList() == null){
					if("String".equals(column.getType())){
						type = codeModel._ref(String.class);
					}else if("Integer".equals(column.getType())){
						type = codeModel._ref(Integer.class);
					}else if("Long".equals(column.getType())){
						type = codeModel._ref(Long.class);
					}else if("Double".equals(column.getType()) || "Percent".equals(column.getType())){
						type = codeModel._ref(Double.class);
					}else if("Boolean".equals(column.getType())){
						type = codeModel._ref(Boolean.class);
					}else if("Date".equals(column.getType()) || "Time".equals(column.getType())){
						type = codeModel._ref(Date.class);
						jc.field(JMod.PRIVATE, codeModel._ref(String.class), field.getName() + "Str");
					}else{
						String clazz = packageGenerated + "." + column.getType() + "Struct";
						type = codeModel._ref(Class.forName(clazz));
						jc.field(JMod.PRIVATE, codeModel.ref(Long.class), field.getName() + "Id");
					}
				}else{
					if("String".equals(column.getType())){
						type = codeModel.ref(java.util.Set.class).narrow(String.class);
					}else if("Integer".equals(column.getType())){
						type = codeModel.ref(java.util.Set.class).narrow(Integer.class);
					}else if("Long".equals(column.getType())){
						type = codeModel._ref(Long.class);
					}else if("Double".equals(column.getType()) || "Percent".equals(column.getType())){
						type = codeModel._ref(Double.class);
					}else if("Boolean".equals(column.getType())){
						type = codeModel.ref(java.util.Set.class).narrow(Boolean.class);
					}else if("Date".equals(column.getType()) || "Time".equals(column.getType())){
						type = codeModel.ref(java.util.Set.class).narrow(Date.class);
					}else{
						String clazz = packageGenerated + "." + column.getType() + "Struct";
						type = codeModel.ref(java.util.Set.class).narrow(codeModel.ref(Class.forName(clazz)));  
						jc.field(JMod.PRIVATE, codeModel.ref(java.util.Set.class).narrow(codeModel.ref(Long.class)), field.getName() + "Id");
						unchecked = true;
					}
				}
				if(type != null){
					jc.field(JMod.PRIVATE, type, field.getName());
				}
				// getter/setter
				method = jc.method(JMod.PUBLIC, type, "get" + StringUtils.capitalize(field.getName()));
				method.body()._if(JExpr.ref(field.getName()).ne(JExpr._null()))._then()._return(JExpr.ref(field.getName()));
				method.body()._return(JExpr.cast(type, JExpr.invoke("getAttributeValues").invoke("get").arg(field.getName())));
				if(unchecked)
					method.annotate(SuppressWarnings.class).param("value", "unchecked");
				// Transient get Date String method
				if("Date".equals(column.getType()) || "Time".equals(column.getType())){
					JMethod method2 = jc.method(JMod.PUBLIC, codeModel.ref(String.class), "get" + StringUtils.capitalize(field.getName()) + "Str");
					method2.body()._return(JExpr.ref(field.getName() + "Str"));
					if(entity.getPersist())
						method2.annotate(Transient.class);
				}
				// For transient
				if(column.getTransience() && entity.getPersist()){
					method.annotate(Transient.class);
				}else{
					// For normal column
					if(column.getList() == null && column.getManyToOne() == null && column.getOneToOne() == null){
						if(entity.getPersist()){
							annotation = method.annotate(javax.persistence.Column.class);
							if(column.getUnique()){
								annotation.param("unique", true);
							}
							if(column.getLength() != null){
								annotation.param("length", column.getLength());
							}
							if(column.getIndex() != null){
								method.annotate(Index.class).param("name", column.getIndex());
							}
						}
					// For ManyToOne
					}else if(column.getManyToOne() != null){
						ManyToOne mto = column.getManyToOne();
						if(entity.getPersist()){
							annotation = method.annotate(javax.persistence.ManyToOne.class);
							if(mto.getFetch().equals("eager")){
								annotation.param("fetch", FetchType.EAGER);
							}else{
								annotation.param("fetch", FetchType.LAZY);
							}
							if(mto.getCascade() == null){
								
							}else if(mto.getCascade().equals("persist")){
								annotation.param("cascade", CascadeType.PERSIST);
							}else if(mto.getCascade().equals("merge")){
								annotation.param("cascade", CascadeType.MERGE);
							}else if(mto.getCascade().equals("refresh")){
								annotation.param("cascade", CascadeType.REFRESH);
							}else if(mto.getCascade().equals("remove")){
								annotation.param("cascade", CascadeType.REMOVE);
							}else{
								annotation.param("cascade", CascadeType.ALL);
							}
							annotation.param("optional", mto.getOptional());
							if(mto.getJoinColumn() != null){
								annotation = method.annotate(javax.persistence.JoinColumn.class);
								annotation.param("name", mto.getJoinColumn());
								annotation.param("referencedColumnName", mto.getReferencedColumn());
							}
						}
						// For transient reference entity ID getter/setter
						JType type2 = codeModel.ref(Long.class);
						method = jc.method(JMod.PUBLIC, type2, "get" + StringUtils.capitalize(field.getName()) + "Id");
						if(entity.getPersist())
							method.annotate(Transient.class);
						method.body()._if(JExpr.ref(field.getName() + "Id").ne(JExpr._null()))._then()._return(JExpr.ref(field.getName() + "Id"));
						method.body()._if(JExpr.ref(field.getName()).ne(JExpr._null()))._then()._return(JExpr.ref(field.getName()).invoke("getId"));
						method.body()._return(JExpr._null());
						method = jc.method(JMod.PUBLIC, codeModel.VOID, "set" + StringUtils.capitalize(field.getName()) + "Id");
						method.param(type2, field.getName() + "Id");
						method.body().directStatement("this." + field.getName() + "Id=" + field.getName() + "Id;");
						method.body().invoke("setAttributeValue").arg(field.getName() + "Id").arg(JExpr.ref(field.getName() + "Id"));
						method.body()._if(JExpr.ref(field.getName() + "Id").eq(JExpr._null()))._then()._return();
						JType type3 = codeModel._ref(Class.forName(packageGenerated + "." + column.getType() + "Struct"));
						method.body().decl(type3, field.getName(), JExpr._new(type3));
						method.body().invoke(JExpr.ref(field.getName()), "setId").arg(JExpr.ref(field.getName() + "Id"));
						method.body().invoke("set" + StringUtils.capitalize(field.getName())).arg(JExpr.ref(field.getName()));
					// For OneToOne	
					}else if(column.getOneToOne() != null){
						
					// For ManyToMany
					}else if(column.getList().equals("ManyToMany")){
						ManyToMany mtm = column.getManyToMany();
						if(entity.getPersist()){
							annotation = method.annotate(javax.persistence.ManyToMany.class);
							if(mtm.getFetch().equals("eager")){
								annotation.param("fetch", FetchType.EAGER);
							}else{
								annotation.param("fetch", FetchType.LAZY);
							}
							if(mtm.getJoinTable() != null){
								annotation = method.annotate(javax.persistence.JoinTable.class);
								annotation.param("name", mtm.getJoinTable());
								if(mtm.getJoinColumns() != null){
									JAnnotationArrayMember  aam = annotation.paramArray("joinColumns");
									for(String joinColumn : mtm.getJoinColumns().split(",")){
										aam.annotate(javax.persistence.JoinColumn.class).param("name", joinColumn);
									}
								}
								if(mtm.getInverseJoinColumns() != null){
									JAnnotationArrayMember  aam = annotation.paramArray("inverseJoinColumns");
									for(String rjoinColumn : mtm.getInverseJoinColumns().split(",")){
										aam.annotate(javax.persistence.JoinColumn.class).param("name", rjoinColumn);
									}
								}
							}
							annotation = method.annotate(org.hibernate.annotations.Cache.class);
							annotation.param("usage", CacheConcurrencyStrategy.READ_WRITE);
						}
						// For transient reference entity ID getter/setter
						JType type2 = codeModel.ref(java.util.Set.class).narrow(codeModel.ref(Long.class));
						method = jc.method(JMod.PUBLIC, type2, "get" + StringUtils.capitalize(field.getName()) + "Id");
						if(entity.getPersist())
							method.annotate(Transient.class);
						method.body()._if(JExpr.ref(field.getName() + "Id").ne(JExpr._null()))._then()._return(JExpr.ref(field.getName() + "Id"));
						method.body().decl(type2, field.getName() + "Id", JExpr._new(codeModel.ref(java.util.HashSet.class).narrow(codeModel.ref(Long.class))));
						method.body()._if(JExpr.invoke("get" + StringUtils.capitalize(field.getName())).ne(JExpr._null()))._then()
								.forEach(codeModel._ref(Class.forName(packageGenerated + "." + column.getType() + "Struct")), "entity",JExpr.invoke("get" + StringUtils.capitalize(field.getName())))
									.body().invoke(JExpr.ref(field.getName() + "Id"), "add").arg(JExpr.ref("entity").invoke("getId"));
						method.body()._return(JExpr.ref(field.getName() + "Id"));
						method = jc.method(JMod.PUBLIC, codeModel.VOID, "set" + StringUtils.capitalize(field.getName()) + "Id");
						method.param(type2, field.getName() + "Id");
						method.body().directStatement("this." + field.getName() + "Id=" + field.getName() + "Id;");
						method.body().invoke("setAttributeValue").arg(field.getName() + "Id").arg(JExpr.ref(field.getName() + "Id"));
						JType type3 = codeModel._ref(Class.forName(packageGenerated + "." + column.getType() + "Struct"));
						JBlock body2 = method.body()._if(JExpr.ref(field.getName() + "Id").ne(JExpr._null()))._then();
						body2.invoke("set" + StringUtils.capitalize(field.getName())).arg(JExpr._new(codeModel.ref(HashSet.class).narrow(type3)));
						JBlock body3 = body2.forEach(codeModel.ref(Long.class), "entityId", JExpr.ref(field.getName() + "Id")).body();
						body3.decl(type3, "entity", JExpr._new(type3));
						body3.invoke(JExpr.ref("entity"), "setId").arg(JExpr.ref("entityId"));
						body3.invoke(JExpr.invoke("get" + StringUtils.capitalize(field.getName())), "add").arg(JExpr.ref("entity"));
					// For OneToMany
					}else if(column.getList().equals("OneToMany")){
						OneToMany otm = column.getOneToMany();
						if(entity.getPersist()){
							annotation = method.annotate(javax.persistence.OneToMany.class);
							if(otm.getMappedBy() != null){
								annotation.param("mappedBy", otm.getMappedBy());
							}
							if(otm.getFetch().equals("eager")){
								annotation.param("fetch", FetchType.EAGER);
							}else{
								annotation.param("fetch", FetchType.LAZY);
							}
							if(otm.getCascade() == null){
								
							}else if(otm.getCascade().equals("persist")){
								annotation.param("cascade", CascadeType.PERSIST);
							}else if(otm.getCascade().equals("merge")){
								annotation.param("cascade", CascadeType.MERGE);
							}else if(otm.getCascade().equals("refresh")){
								annotation.param("cascade", CascadeType.REFRESH);
							}else if(otm.getCascade().equals("remove")){
								annotation.param("cascade", CascadeType.REMOVE);
							}else if(otm.getCascade().equals("all")){
								annotation.param("cascade", CascadeType.ALL);
							}
							if(otm.getOrderBy() != null){
								annotation = method.annotate(javax.persistence.OrderBy.class);
								annotation.param("value", otm.getOrderBy());
							}
						}
						// For transient reference entity ID getter/setter
						JType type2 = codeModel.ref(java.util.Set.class).narrow(codeModel.ref(Long.class));
						method = jc.method(JMod.PUBLIC, type2, "get" + StringUtils.capitalize(field.getName()) + "Id");
						if(entity.getPersist())
							method.annotate(Transient.class);
						method.body()._if(JExpr.ref(field.getName() + "Id").ne(JExpr._null()))._then()._return(JExpr.ref(field.getName() + "Id"));
						method.body().decl(type2, field.getName() + "Id", JExpr._new(codeModel.ref(java.util.HashSet.class).narrow(codeModel.ref(Long.class))));
						method.body()._if(JExpr.invoke("get" + StringUtils.capitalize(field.getName())).ne(JExpr._null()))._then()
								.forEach(codeModel._ref(Class.forName(packageGenerated + "." + column.getType() + "Struct")), "entity",JExpr.invoke("get" + StringUtils.capitalize(field.getName())))
									.body().invoke(JExpr.ref(field.getName() + "Id"), "add").arg(JExpr.ref("entity").invoke("getId"));
						method.body()._return(JExpr.ref(field.getName() + "Id"));
						method = jc.method(JMod.PUBLIC, codeModel.VOID, "set" + StringUtils.capitalize(field.getName()) + "Id");
						method.param(type2, field.getName() + "Id");
						method.body().directStatement("this." + field.getName() + "Id=" + field.getName() + "Id;");
						method.body().invoke("setAttributeValue").arg(field.getName() + "Id").arg(JExpr.ref(field.getName() + "Id"));
						JType type3 = codeModel._ref(Class.forName(packageGenerated + "." + column.getType() + "Struct"));
						JBlock body2 = method.body()._if(JExpr.ref(field.getName() + "Id").ne(JExpr._null()))._then();
						body2.invoke("set" + StringUtils.capitalize(field.getName())).arg(JExpr._new(codeModel.ref(HashSet.class).narrow(type3)));
						JBlock body3 = body2.forEach(codeModel.ref(Long.class), "entityId", JExpr.ref(field.getName() + "Id")).body();
						body3.decl(type3, "entity", JExpr._new(type3));
						body3.invoke(JExpr.ref("entity"), "setId").arg(JExpr.ref("entityId"));
						body3.invoke(JExpr.invoke("get" + StringUtils.capitalize(field.getName())), "add").arg(JExpr.ref("entity"));
					// For CollectionOfElements
					}else if(column.getList().equals("CollectionOfElements")){
						if(entity.getPersist()){
							CollectionOfElements coe = column.getCollectionOfElements();
							annotation = method.annotate(org.hibernate.annotations.CollectionOfElements.class);
							if(coe.getFetch().equals("eager")){
								annotation.param("fetch", FetchType.EAGER);
							}else{
								annotation.param("fetch", FetchType.LAZY);
							}
							if(coe.getJoinTable() != null){
								annotation = method.annotate(javax.persistence.JoinTable.class);
								annotation.param("name", coe.getJoinTable());
							}
							annotation = method.annotate(org.hibernate.annotations.Cache.class);
							annotation.param("usage", CacheConcurrencyStrategy.READ_WRITE);
						}
					}
				}
				method = jc.method(JMod.PUBLIC, codeModel.VOID, "set" + StringUtils.capitalize(field.getName()));
				method.param(type, field.getName());
				body = method.body();
				body.directStatement("this." + field.getName() + " = " + field.getName() + ";");
				invocation = body.invoke("setAttributeValue");
				invocation.arg(field.getName());
				invocation.arg(JExpr.ref(field.getName()));
				// Transient set Date String method
				if("Date".equals(column.getType()) || "Time".equals(column.getType())){
					String sdf = "yyyy-MM-dd";
					if("Time".equals(column.getType()))
						sdf = "yyyy-MM-dd HH:mm";
					method = jc.method(JMod.PUBLIC, codeModel.VOID, "set" + StringUtils.capitalize(field.getName()) + "Str");
					method.param(codeModel.ref(String.class), field.getName());
					body = method.body();
					body.directStatement("this." + field.getName() + "Str = " + field.getName() + ";");
					invocation = body.invoke("setAttributeValue");
					invocation.arg(field.getName() + "Str");
					invocation.arg(JExpr.ref(field.getName()));
					body._if(codeModel.ref(StringUtils.class).staticInvoke("isBlank").arg(JExpr.ref(field.getName())))._then()._return();
					body.decl(codeModel.ref(SimpleDateFormat.class), "format"
							, JExpr._new(codeModel.ref(SimpleDateFormat.class)).arg(sdf).arg(JExpr._new(codeModel.ref(Locale.class)).arg("en")));
					JTryBlock tryBlock = body._try();
					tryBlock.body().invoke("set" + StringUtils.capitalize(field.getName())).arg(JExpr.ref("format").invoke("parse").arg(JExpr.ref(field.getName())));
					tryBlock._catch(codeModel.ref(java.text.ParseException.class));
				}
				methodClear.body().directStatement("this." + field.getName() + " = null;");
			}
		}
		// Customized Struct
		if(entity.getCustomized() != null && entity.getCustomized().indexOf("model") != -1){
			String clazz = packageCustomized + ".Customized" + entity.getName() + "Struct";
			JType type = codeModel._ref(Class.forName(clazz));
			jc.field(JMod.PRIVATE, type, "customizedStruct");
			method = jc.method(JMod.PUBLIC, type, "getCustomizedStruct");
			method.annotate(Transient.class);
			body = method.body();
			body.directStatement("if(customizedStruct == null){");
			body.directStatement("    customizedStruct = new Customized" + entity.getName() + "Struct(this);");
			body.directStatement("}");
			body._return(JExpr.ref("customizedStruct"));
			method = jc.method(JMod.PUBLIC, codeModel.VOID, "setCustomizedStruct");
			method.param(type, "customizedStruct");
			body = method.body();
			body.directStatement("this.customizedStruct = customizedStruct;");
		}
       	codeModel.build(new File("src/main/java"));
	}
	
	/**
	 * Generate persist DAO {Entity}Dao.java files
	 * @param entity
	 * @throws JClassAlreadyExistsException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public void generatePersistDao(Entity entity) throws JClassAlreadyExistsException, ClassNotFoundException, IOException{
		String packageGenerated = basePackage + ".generated.dao";
		JCodeModel codeModel = new JCodeModel();
		// Class
		JPackage jp = codeModel._package(packageGenerated);
		JDefinedClass jc = jp._class(entity.getName() + "Dao");
		jc._extends(codeModel.ref(GenericHibernateDao.class)
				.narrow(Class.forName(basePackage + ".generated.model." + entity.getName() + "Struct"), Long.class));
		JDocComment jDocComment = jc.javadoc();
        jDocComment.add("Generated class based on BusinessModel.xml");
        if(entity.getCustomized() == null || entity.getCustomized().indexOf("dao") == -1){
			jc.annotate(Component.class);
		}
		codeModel.build(new File("src/main/java"));
	}
	
	/**
	 * Generate service layer {Entity}Service.java files
	 * @param entity
	 * @throws JClassAlreadyExistsException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public void generateService(Entity entity) throws JClassAlreadyExistsException, ClassNotFoundException, IOException{
		String packageGenerated = basePackage + ".generated.service";
		String entityModelClass = basePackage + ".generated.model." + entity.getName() + "Struct";
		String entityDaoClass = basePackage + ".generated.dao." + entity.getName() + "Dao";
		JCodeModel codeModel = new JCodeModel();
		// Class
		JPackage jp = codeModel._package(packageGenerated);
		JDefinedClass jc = jp._class(entity.getName() + "Service");
		jc._extends(codeModel.ref(AbstractEntityService.class)
				.narrow(Class.forName(entityModelClass), Long.class, Class.forName(entityDaoClass)));
		JDocComment jDocComment = jc.javadoc();
        jDocComment.add("Generated class based on BusinessModel.xml");
        if(entity.getCustomized() == null || entity.getCustomized().indexOf("service") == -1){
			jc.annotate(Transactional.class);
			jc.annotate(Service.class).param("value", StringUtils.uncapitalize(entity.getName() + "Service"));
		}
        if(entity.getCustomized() != null && entity.getCustomized().indexOf("dao") > 0){
        	entityDaoClass = basePackage + ".customized.dao.Customized" + entity.getName() + "Dao";
        }
        // Field
		JType type = codeModel._ref(Class.forName(entityDaoClass));
		jc.field(JMod.PRIVATE, type, "dao").annotate(Autowired.class);
        // Method
		jc.method(JMod.PUBLIC, type, "getDao").body()._return(JExpr.ref("dao"));
		codeModel.build(new File("src/main/java"));
	}
	
	/**
	 * Generate UI layer {Entity}Controller.java files
	 * @param entity
	 * @throws JClassAlreadyExistsException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public void generateController(Entity entity) throws JClassAlreadyExistsException, ClassNotFoundException, IOException{
		String packageGenerated = basePackage + ".generated.controller";
		String packageCustomized = basePackage + ".customized.controller";
		boolean customizedController = false;
		String jspRootPath = "generated/";
		JCodeModel codeModel = new JCodeModel();
		JType entityType = codeModel._ref(Class.forName(basePackage + ".generated.model." + entity.getName() + "Struct"));
		// Class
		JPackage jp = codeModel._package(packageGenerated);
		JDefinedClass jc = jp._class(entity.getName() + "Controller");
		jc.annotate(Controller.class);
		jc.annotate(RequestMapping.class).param("value", "/" + entity.getAlias());
		jc._implements(codeModel.ref(ControllerInterface.class)
				.narrow(Class.forName(basePackage + ".generated.model." + entity.getName() + "Struct")
						, Class.forName(basePackage + ".generated.controller.Search" + entity.getName() + "Command")
						, Long.class));
		JDocComment jDocComment = jc.javadoc();
        jDocComment.add("Generated class based on BusinessModel.xml");
		// Field
		JType type = codeModel._ref(Class.forName(packageGenerated + "." + entity.getName() + "Validator"));
		jc.field(JMod.PRIVATE, type, entity.getAlias() + "Validator").annotate(Autowired.class);
		if(entity.getCustomized() != null && entity.getCustomized().indexOf("service") != -1){
			type = codeModel._ref(Class.forName(basePackage + ".customized.service.Customized" + entity.getName() + "Service"));
		}else{
			type = codeModel._ref(Class.forName(basePackage + ".generated.service." + entity.getName() + "Service"));
		}
		jc.field(JMod.PRIVATE, type, entity.getAlias() + "Service").annotate(Autowired.class);
		type = codeModel._ref(Class.forName(basePackage + ".customized.service.LogService"));
		jc.field(JMod.PRIVATE, type, "logService").annotate(Autowired.class);
		type = codeModel.ref(QueryTableBuilder.class).narrow(Class.forName(basePackage + ".generated.model." + entity.getName() + "Struct"));
		jc.field(JMod.PRIVATE, type, "queryTableBuilder").annotate(Autowired.class);
		type = codeModel.ref(SearchTableBuilder.class).narrow(Class.forName(basePackage + ".generated.model." + entity.getName() + "Struct"));
		jc.field(JMod.PRIVATE, type, "searchTableBuilder").annotate(Autowired.class);
		type = codeModel.ref(DetailTableBuilder.class).narrow(Class.forName(basePackage + ".generated.model." + entity.getName() + "Struct"));
		jc.field(JMod.PRIVATE, type, "detailTableBuilder").annotate(Autowired.class);
		if(entity.getCustomized() != null && entity.getCustomized().indexOf("controller") != -1){
			customizedController = true;
			type = codeModel._ref(Class.forName(packageCustomized + ".Customized" + entity.getName() + "Controller"));
			jc.field(JMod.PRIVATE, type, "customizedController").annotate(Autowired.class);
		}
		if(entity.getCustomized() != null && entity.getCustomized().indexOf("jsp") != -1){
			jspRootPath = "customized/";
		}
		// Method query
		JMethod method = jc.method(JMod.PUBLIC, codeModel._ref(String.class), "query");
		method.annotate(RequestMapping.class).param("value", "/query.o").param("method", RequestMethod.GET);
		method.annotate(RequiresPermissions.class).param("value", entity.getAlias() + ":query");
		method.param(codeModel._ref(Model.class), "model");
		method.param(codeModel._ref(HttpServletRequest.class), "request");
		method.param(codeModel._ref(Class.forName(packageGenerated + ".Search" + entity.getName() + "Command")), "searchCommand").annotate(ModelAttribute.class);
		JBlock body = method.body();
		if(customizedController){
			body.decl(codeModel.ref(String.class), "customizedResult", JExpr.ref("customizedController").invoke("query").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("searchCommand")));
			body._if(JExpr.ref("customizedResult").ne(JExpr._null()))._then()._return(JExpr.ref("customizedResult"));
		}
		body._if(JExpr.ref("searchCommand").eq(JExpr._null()))._then()
			.directStatement("searchCommand = new Search" + entity.getName() + "Command();");
		body.decl(codeModel._ref(Object.class), entity.getAlias() + "Command", JExpr.ref("request").invoke("getSession").invoke("getAttribute").arg(entity.getAlias() + "Command"));
		body.decl(codeModel._ref(Object.class), entity.getAlias() + "Command2", JExpr.ref("request").invoke("getSession").invoke("getAttribute").arg(entity.getAlias() + "Command2"));
		body.decl(codeModel._ref(Object.class), entity.getAlias() + "Operator", JExpr.ref("request").invoke("getSession").invoke("getAttribute").arg(entity.getAlias() + "Operator"));
		body.decl(codeModel._ref(String.class), "offset", JExpr.ref("request").invoke("getParameter").arg("pager.offset"));
		body.decl(codeModel._ref(String.class), "psort", JExpr.ref("request").invoke("getParameter").arg("field.sort"));
		JConditional ifcon = body._if(codeModel.ref(StringUtils.class).staticInvoke("isNotBlank").arg(JExpr.ref("offset")));
		JBlock ifbody = ifcon._then();
		JBlock elsebody = ifcon._else();
		ifbody.invoke(JExpr.ref("searchCommand"), "setOffset").arg(codeModel.ref(Integer.class).staticInvoke("parseInt").arg(JExpr.ref("offset")));
		ifbody._if(JExpr.ref(entity.getAlias() + "Command").ne(JExpr._null()))
			._then().invoke(JExpr.ref("searchCommand"), "setEntity").arg(JExpr.cast(entityType, JExpr.ref(entity.getAlias() + "Command")));
		ifbody._if(JExpr.ref(entity.getAlias() + "Command2").ne(JExpr._null()))
			._then().invoke(JExpr.ref("searchCommand"), "setEntity2").arg(JExpr.cast(entityType, JExpr.ref(entity.getAlias() + "Command2")));
		ifbody._if(JExpr.ref(entity.getAlias() + "Operator").ne(JExpr._null()))
			._then().invoke(JExpr.ref("searchCommand"), "setOperator").arg(JExpr.cast(entityType, JExpr.ref(entity.getAlias() + "Operator")));
		elsebody.invoke(JExpr.ref("request").invoke("getSession"),"setAttribute").arg(entity.getAlias() + "Command").arg(JExpr._null());
		elsebody.invoke(JExpr.ref("request").invoke("getSession"),"setAttribute").arg(entity.getAlias() + "Command2").arg(JExpr._null());
		elsebody.invoke(JExpr.ref("request").invoke("getSession"),"setAttribute").arg(entity.getAlias() + "Operator").arg(JExpr._null());
		elsebody.invoke(JExpr.ref("request").invoke("getSession"),"setAttribute").arg(entity.getAlias() + "Sort").arg(JExpr._null());
		body._if(codeModel.ref(StringUtils.class).staticInvoke("isNotBlank").arg(JExpr.ref("psort")))
			._then().invoke(JExpr.ref("request").invoke("getSession"),"setAttribute").arg(entity.getAlias() + "Sort").arg(JExpr.ref("psort"));
		body.decl(codeModel._ref(Object.class), entity.getAlias() + "Sort", JExpr.ref("request").invoke("getSession").invoke("getAttribute").arg(entity.getAlias() + "Sort"));
		body._if(JExpr.ref(entity.getAlias() + "Sort").ne(JExpr._null()))._then()
			.invoke(JExpr.ref("searchCommand"), "setPropertiesSorted").arg(JExpr.newArray(codeModel.ref(String.class)).add(JExpr.ref(entity.getAlias() + "Sort").invoke("toString")));
		body.decl(codeModel.ref(List.class).narrow(entityType), "list", JExpr.ref(entity.getAlias() + "Service").invoke("search").arg(JExpr.ref("searchCommand")));
		body.invoke(JExpr.ref("request"), "setAttribute").arg("entities").arg(JExpr.ref("list"));
		body.invoke(JExpr.ref("request"),"setAttribute").arg("searchCommand").arg(JExpr.ref("searchCommand"));
		body.invoke(JExpr.ref("request"),"setAttribute").arg(entity.getAlias() + "QueryTable")
			.arg(JExpr.ref("queryTableBuilder").invoke("build").arg(JExpr.ref("request")).arg(entity.getName()).arg(JExpr.ref("list"))
					.arg(JExpr.cast(codeModel.ref(String.class), JExpr.ref(entity.getAlias() + "Sort"))).arg(JExpr.ref(entity.getAlias() + "Validator").invoke("getMessageSource")));
		body.invoke(JExpr.ref("request"),"setAttribute").arg(entity.getAlias() + "SearchTable")
			.arg(JExpr.ref("searchTableBuilder").invoke("build")
					.arg(JExpr.ref("request")).arg(JExpr.ref("searchCommand")).arg(JExpr.ref(entity.getAlias() + "Validator").invoke("getMessageSource")));
		body._return(JExpr.lit(jspRootPath + entity.getAlias() + "/query"));
		// Method search
		method = jc.method(JMod.PUBLIC, codeModel._ref(String.class), "search");
		method.annotate(RequestMapping.class).param("value", "/query.o").param("method", RequestMethod.POST);
		method.annotate(RequiresPermissions.class).param("value", entity.getAlias() + ":query");
		method.param(codeModel._ref(Model.class), "model");
		method.param(codeModel._ref(HttpServletRequest.class), "request");
		method.param(codeModel._ref(Class.forName(packageGenerated + ".Search" + entity.getName() + "Command")), "searchCommand").annotate(ModelAttribute.class);
		body = method.body();
		if(customizedController){
			body.decl(codeModel.ref(String.class), "customizedResult", JExpr.ref("customizedController").invoke("search").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("searchCommand")));
			body._if(JExpr.ref("customizedResult").ne(JExpr._null()))._then()._return(JExpr.ref("customizedResult"));
		}
		body.invoke(JExpr.ref("request").invoke("getSession"),"setAttribute").arg(entity.getAlias() + "Command").arg(JExpr.ref("searchCommand").invoke("getEntity"));
		body.invoke(JExpr.ref("request").invoke("getSession"),"setAttribute").arg(entity.getAlias() + "Command2").arg(JExpr.ref("searchCommand").invoke("getEntity2"));
		body.invoke(JExpr.ref("request").invoke("getSession"),"setAttribute").arg(entity.getAlias() + "Operator").arg(JExpr.ref("searchCommand").invoke("getOperator"));
		body._return(JExpr.lit("redirect:/" + entity.getAlias() + "/query.o?pager.offset=0"));
		// Method create
		method = jc.method(JMod.PUBLIC, codeModel._ref(String.class), "create");
		method.annotate(RequestMapping.class).param("value", "/create.o").param("method", RequestMethod.GET);
		method.annotate(RequiresPermissions.class).param("value", entity.getAlias() + ":create");
		method.param(codeModel._ref(Model.class), "model");
		method.param(codeModel._ref(HttpServletRequest.class), "request");
		method.param(entityType, "entity").annotate(ModelAttribute.class);
		body = method.body();
		body.directStatement("entity.setId(null);");
		if(customizedController){
			body.decl(codeModel.ref(String.class), "customizedResult", JExpr.ref("customizedController").invoke("create").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("entity")));
			body._if(JExpr.ref("customizedResult").ne(JExpr._null()))._then()._return(JExpr.ref("customizedResult"));
		}
		body.invoke(JExpr.ref("model"), "addAttribute").arg("ACTION").arg("create");
		body.invoke(JExpr.ref("request"),"setAttribute").arg(entity.getAlias() + "DetailTable")
			.arg(JExpr.ref("detailTableBuilder").invoke("build")
					.arg(JExpr.ref("request")).arg(JExpr.ref("entity")).arg(JExpr.ref(entity.getAlias() + "Validator").invoke("getMessageSource")).arg(JExpr.FALSE));
		body.invoke(JExpr.ref("request"),"setAttribute").arg(entity.getAlias() + "DetailButtons")
			.arg(JExpr.ref("detailTableBuilder").invoke("buildButton")
					.arg(JExpr.ref("request")).arg(JExpr.ref("entity")).arg(JExpr.ref(entity.getAlias() + "Validator").invoke("getMessageSource")).arg(JExpr.FALSE));
		body._return(JExpr.lit(jspRootPath + entity.getAlias() + "/edit"));
		// Method create save
		method = jc.method(JMod.PUBLIC, codeModel._ref(String.class), "saveCreate");
		method.annotate(RequestMapping.class).param("value", "/create.o").param("method", RequestMethod.POST);
		method.annotate(RequiresPermissions.class).param("value", entity.getAlias() + ":create");
		method.param(codeModel._ref(Model.class), "model");
		method.param(codeModel._ref(HttpServletRequest.class), "request");
		method.param(entityType, "entity").annotate(ModelAttribute.class);
		method.param(BindingResult.class, "errors");
		body = method.body();
		body.decl(codeModel._ref(String.class), "offset", JExpr.ref("request").invoke("getParameter").arg("pager.offset"));
		ifcon = body._if(codeModel.ref(StringUtils.class).staticInvoke("isNotBlank").arg(JExpr.ref("offset")));
		ifcon._then().directStatement("offset = \"?pager.offset=\" + offset;");
		ifcon._else().directStatement("offset = \"\";");
		body.invoke(JExpr.ref(entity.getAlias() + "Validator"), "validate").arg(JExpr.ref("entity")).arg(JExpr.ref("errors"));
		ifbody = body._if(JExpr.ref("errors").invoke("hasErrors"))._then();
		ifbody._return(JExpr.invoke("create").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("entity")));
		if(customizedController){
			body.decl(codeModel.ref(String.class), "customizedResult", JExpr.ref("customizedController").invoke("saveCreate").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("entity")).arg(JExpr.ref("errors")));
			body._if(JExpr.ref("customizedResult").ne(JExpr._null()))._then()._return(JExpr.ref("customizedResult"));
		}
		JTryBlock tryblock = body._try();
		tryblock.body().invoke(JExpr.ref(entity.getAlias() + "Service"), "createEntity").arg(JExpr.ref("entity"));
		tryblock.body().invoke(JExpr.ref("logService"), "log").arg(entity.getAlias()).arg("create").arg(JExpr.ref("entity").invoke("getAttributeValues").invoke("get").arg("name"));
		JBlock catchBody = tryblock._catch(codeModel.ref(DataIntegrityViolationException.class)).body();
		catchBody.invoke(JExpr.ref("_x"), "printStackTrace");
		catchBody.invoke(JExpr.ref("errors"), "rejectValue").arg("id").arg("error.common.model.exist").arg("The entity has already existed.");
		catchBody._return(JExpr.invoke("create").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("entity")));
		body._return(JExpr.lit("redirect:/" + entity.getAlias() + "/query.o").plus(JExpr.ref("offset")));
		// Method edit
		method = jc.method(JMod.PUBLIC, codeModel._ref(String.class), "edit");
		method.annotate(RequestMapping.class).param("value", "/edit.o").param("method", RequestMethod.GET);
		method.annotate(RequiresPermissions.class).param("value", entity.getAlias() + ":edit");
		method.param(codeModel._ref(Model.class), "model");
		method.param(codeModel._ref(HttpServletRequest.class), "request");
		method.param(codeModel._ref(Long.class), "id").annotate(RequestParam.class);
		method.param(entityType, "entity").annotate(ModelAttribute.class);
		body = method.body();
		body.invoke(JExpr.ref("model"), "addAttribute").arg("ACTION").arg("edit");
		body.decl(entityType, "entity2", JExpr.ref(entity.getAlias() + "Service").invoke("getEntity").arg(JExpr.ref("id")));
		tryblock = body._try();
		tryblock.body().staticInvoke(codeModel.ref(BeanUtils.class), "copyProperty").arg(JExpr.ref("entity")).arg(JExpr.lit("attributeValues")).arg(JExpr.ref("entity2").invoke("getAttributeValues"));
		tryblock._catch(codeModel.ref(Exception.class)).body().directStatement("_x.printStackTrace();");
		if(customizedController){
			body.decl(codeModel.ref(String.class), "customizedResult", JExpr.ref("customizedController").invoke("edit").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("id")).arg(JExpr.ref("entity")));
			body._if(JExpr.ref("customizedResult").ne(JExpr._null()))._then()._return(JExpr.ref("customizedResult"));
		}
		body.invoke(JExpr.ref("request"),"setAttribute").arg(entity.getAlias() + "DetailTable")
			.arg(JExpr.ref("detailTableBuilder").invoke("build")
					.arg(JExpr.ref("request")).arg(JExpr.ref("entity")).arg(JExpr.ref(entity.getAlias() + "Validator").invoke("getMessageSource")).arg(JExpr.FALSE));
		body.invoke(JExpr.ref("request"),"setAttribute").arg(entity.getAlias() + "DetailButtons")
			.arg(JExpr.ref("detailTableBuilder").invoke("buildButton")
					.arg(JExpr.ref("request")).arg(JExpr.ref("entity")).arg(JExpr.ref(entity.getAlias() + "Validator").invoke("getMessageSource")).arg(JExpr.FALSE));
		body._return(JExpr.lit(jspRootPath + entity.getAlias() + "/edit"));
		// Method edit save
		method = jc.method(JMod.PUBLIC, codeModel._ref(String.class), "saveEdit");
		method.annotate(RequestMapping.class).param("value", "/edit.o").param("method", RequestMethod.POST);
		method.annotate(RequiresPermissions.class).param("value", entity.getAlias() + ":edit");
		method.param(codeModel._ref(Model.class), "model");
		method.param(codeModel._ref(HttpServletRequest.class), "request");
		method.param(codeModel._ref(Long.class), "id").annotate(RequestParam.class);
		method.param(entityType, "entity").annotate(ModelAttribute.class);
		method.param(BindingResult.class, "errors");
		body = method.body();
		body.decl(codeModel._ref(String.class), "offset", JExpr.ref("request").invoke("getParameter").arg("pager.offset"));
		ifcon = body._if(codeModel.ref(StringUtils.class).staticInvoke("isNotBlank").arg(JExpr.ref("offset")));
		ifcon._then().directStatement("offset = \"?pager.offset=\" + offset;");
		ifcon._else().directStatement("offset = \"\";");
		body.invoke(JExpr.ref("entity"), "setId").arg(JExpr.ref("id"));
		if(customizedController){
			body.decl(codeModel.ref(String.class), "customizedResult", JExpr.ref("customizedController").invoke("saveEdit").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("id")).arg(JExpr.ref("entity")).arg(JExpr.ref("errors")));
			body._if(JExpr.ref("customizedResult").ne(JExpr._null()))._then()._return(JExpr.ref("customizedResult"));
		}
		body.invoke(JExpr.ref(entity.getAlias() + "Validator"), "validate").arg(JExpr.ref("entity")).arg(JExpr.ref("errors"));
		ifbody = body._if(JExpr.ref("errors").invoke("hasErrors"))._then();
		ifbody._return(JExpr.invoke("edit").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("id")).arg(JExpr.ref("entity")));
		tryblock = body._try();
		tryblock.body().invoke(JExpr.ref(entity.getAlias() + "Service"), "updateEntity").arg(JExpr.ref("entity"));
		tryblock.body().invoke(JExpr.ref("logService"), "log").arg(entity.getAlias()).arg("edit").arg(JExpr.ref("entity").invoke("getAttributeValues").invoke("get").arg("name"));
		catchBody = tryblock._catch(codeModel.ref(UncategorizedSQLException.class)).body();
		catchBody.invoke(JExpr.ref("_x"), "printStackTrace");
		catchBody.invoke(JExpr.ref("errors"), "rejectValue").arg("id").arg("error.common.model.exist").arg("The entity has already existed.");
		catchBody._return(JExpr.invoke("edit").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("id")).arg(JExpr.ref("entity")));
		body._return(JExpr.lit("redirect:/" + entity.getAlias() + "/query.o").plus(JExpr.ref("offset")));
		// Method view
		method = jc.method(JMod.PUBLIC, codeModel._ref(String.class), "view");
		method.annotate(RequestMapping.class).param("value", "/view.o").param("method", RequestMethod.GET);
		method.annotate(RequiresPermissions.class).param("value", entity.getAlias() + ":query");
		method.param(codeModel._ref(Model.class), "model");
		method.param(codeModel._ref(HttpServletRequest.class), "request");
		method.param(codeModel._ref(Long.class), "id").annotate(RequestParam.class);
		method.param(entityType, "entity").annotate(ModelAttribute.class);
		body = method.body();
		body.invoke(JExpr.ref("model"), "addAttribute").arg("ACTION").arg("view");
		body.decl(entityType, "entity2", JExpr.ref(entity.getAlias() + "Service").invoke("getEntity").arg(JExpr.ref("id")));
		tryblock = body._try();
		tryblock.body().staticInvoke(codeModel.ref(BeanUtils.class), "copyProperty").arg(JExpr.ref("entity")).arg(JExpr.lit("attributeValues")).arg(JExpr.ref("entity2").invoke("getAttributeValues"));
		tryblock._catch(codeModel.ref(Exception.class)).body().directStatement("_x.printStackTrace();");
		if(customizedController){
			body.decl(codeModel.ref(String.class), "customizedResult", JExpr.ref("customizedController").invoke("view").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("id")).arg(JExpr.ref("entity")));
			body._if(JExpr.ref("customizedResult").ne(JExpr._null()))._then()._return(JExpr.ref("customizedResult"));
		}
		body.invoke(JExpr.ref("request"),"setAttribute").arg(entity.getAlias() + "DetailTable")
			.arg(JExpr.ref("detailTableBuilder").invoke("build")
					.arg(JExpr.ref("request")).arg(JExpr.ref("entity")).arg(JExpr.ref(entity.getAlias() + "Validator").invoke("getMessageSource")).arg(JExpr.TRUE));
		body.invoke(JExpr.ref("request"),"setAttribute").arg(entity.getAlias() + "DetailButtons")
			.arg(JExpr.ref("detailTableBuilder").invoke("buildButton")
					.arg(JExpr.ref("request")).arg(JExpr.ref("entity")).arg(JExpr.ref(entity.getAlias() + "Validator").invoke("getMessageSource")).arg(JExpr.TRUE));
		body._return(JExpr.lit(jspRootPath + entity.getAlias() + "/edit"));
		// Method delete
		method = jc.method(JMod.PUBLIC, codeModel._ref(String.class), "delete");
		method.annotate(RequestMapping.class).param("value", "/delete.o").param("method", RequestMethod.GET);
		method.annotate(RequiresPermissions.class).param("value", entity.getAlias() + ":delete");
		method.param(codeModel._ref(Model.class), "model");
		method.param(codeModel._ref(HttpServletRequest.class), "request");
		method.param(codeModel._ref(Long.class), "id").annotate(RequestParam.class);
		body = method.body();
		body.decl(codeModel._ref(String.class), "offset", JExpr.ref("request").invoke("getParameter").arg("pager.offset"));
		ifcon = body._if(codeModel.ref(StringUtils.class).staticInvoke("isNotBlank").arg(JExpr.ref("offset")));
		ifcon._then().directStatement("offset = \"?pager.offset=\" + offset;");
		ifcon._else().directStatement("offset = \"\";");
		if(customizedController){
			body.decl(codeModel.ref(String.class), "customizedResult", JExpr.ref("customizedController").invoke("delete").arg(JExpr.ref("model")).arg(JExpr.ref("request")).arg(JExpr.ref("id")));
			body._if(JExpr.ref("customizedResult").ne(JExpr._null()))._then()._return(JExpr.ref("customizedResult"));
		}
		body.decl(entityType, "entity", JExpr.ref(entity.getAlias() + "Service").invoke("getEntity").arg(JExpr.ref("id")));
		body.invoke(JExpr.ref(entity.getAlias() + "Service"), "deleteEntity").arg(JExpr.ref("id"));
		body.invoke(JExpr.ref("logService"), "log").arg(entity.getAlias()).arg("delete").arg(JExpr.ref("entity").invoke("getAttributeValues").invoke("get").arg("name"));
		body._return(JExpr.lit("redirect:/" + entity.getAlias() + "/query.o").plus(JExpr.ref("offset")));
		codeModel.build(new File("src/main/java"));
	}
	
	/**
	 * Generate UI search model Search{Entity}Struct.java files
	 * @param entity
	 * @throws JClassAlreadyExistsException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public void generateSearchCommand(Entity entity) throws JClassAlreadyExistsException, ClassNotFoundException, IOException{
		String packageGenerated = basePackage + ".generated.controller";
		JCodeModel codeModel = new JCodeModel();
		JType type = codeModel._ref(Class.forName( basePackage + ".generated.model." + entity.getName() + "Struct"));
		// Class
		JPackage jp = codeModel._package(packageGenerated);
		JDefinedClass jc = jp._class("Search" + entity.getName() + "Command");
		jc._extends(codeModel.ref(AbstractSearchCommand.class).narrow(type));
		JDocComment jDocComment = jc.javadoc();
        jDocComment.add("Generated class based on BusinessModel.xml");
		// Constructor
		jc.constructor(JMod.PUBLIC).body().invoke("setEntity").arg(JExpr._new(type));
		codeModel.build(new File("src/main/java"));
	}
	
	/**
	 * Generate UI layer {Entity}Valicator.java file
	 * @param entity
	 * @throws JClassAlreadyExistsException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public void generateValidator(Entity entity) throws JClassAlreadyExistsException, ClassNotFoundException, IOException{
		String packageGenerated = basePackage + ".generated.controller";
		String packageCustomized = basePackage + ".customized.controller";
		JCodeModel codeModel = new JCodeModel();
		// Class
		JPackage jp = codeModel._package(packageGenerated);
		JDefinedClass jc = jp._class(entity.getName() + "Validator");
		jc._extends(AbstractValidator.class);
		jc.annotate(Component.class);
		JDocComment jDocComment = jc.javadoc();
        jDocComment.add("Generated class based on BusinessModel.xml");
		JMethod method = null;
		JBlock body = null;
		JInvocation invocation = null;
		JBlock block = null;
		// Method
		method = jc.method(JMod.PUBLIC, codeModel.BOOLEAN, "supports");
		method.annotate(Override.class);
		method.annotate(SuppressWarnings.class).param("value", "rawtypes");
		method.param(codeModel._ref(Class.class), "aClass");
		body = method.body();
		String entityClazz = basePackage + ".generated.model." + entity.getName() + "Struct";
		JType entityType = codeModel._ref(Class.forName(entityClazz));
		body._return(JExpr.direct(entityType.name() + ".class.isAssignableFrom(aClass)"));
		method = jc.method(JMod.PUBLIC, codeModel.VOID, "validate");
		method.annotate(Override.class);
		method.param(codeModel._ref(Object.class), "o");
		method.param(codeModel._ref(Errors.class), "errors");
		body = method.body();
		body.decl(entityType, "entity", JExpr.cast(entityType, JExpr.ref("o")));
		boolean unused = true;
		for(Field field : entity.getFields()){
			ElementDetail element = field.getElementDetail();
			String getMethod = "get" + StringUtils.capitalize(field.getName());
			body.directStatement("// " + field.getName());
			JArray obay = JExpr.newArray(codeModel.ref(Object.class)).add(JExpr.ref("messageSource").invoke("getMessage")
					.arg("label." + entity.getAlias() + ".field." + field.getName())
					.arg(JExpr.newArray(codeModel.ref(Object.class)))
					.arg(codeModel.ref(LocaleContextHolder.class).staticInvoke("getLocale")));
			// Null check
			if(field.getRequired() != null && field.getRequired()){
				boolean referModel = false;
				if(!"String".equals(field.getColumn().getType())
						&& !"Integer".equals(field.getColumn().getType()) 
						&& !"Long".equals(field.getColumn().getType()) 
						&& !"Date".equals(field.getColumn().getType()) 
						&& !"Time".equals(field.getColumn().getType())){
					referModel = true;
				}
				invocation = body.staticInvoke(codeModel.ref(ValidationUtils.class), "rejectIfEmptyOrWhitespace");
				invocation.arg(JExpr.ref("errors"))
						.arg(field.getName() + (referModel?"Id":""))
						.arg("error.common.field.empty")
						.arg(obay)
						.arg("Please specify a " + field.getName());
			}
			// Number check
			if(element != null && element.getInputElement() != null && element.getInputElement().getDatatype() != null && element.getInputElement().getDatatype().equals("Number")){
				unused = false;
				invocation = codeModel.ref(NumberUtils.class).staticInvoke("isNumber");
				invocation.arg(codeModel.ref(String.class).staticInvoke("valueOf").arg(JExpr.ref("entity").invoke("get" + StringUtils.capitalize(field.getName()))));
				invocation = body._if(JExpr.ref("errors").invoke("hasFieldErrors").arg(field.getName()))._then().invoke(JExpr.ref("errors"), "rejectValue");
				invocation.arg(field.getName())
						.arg("error.common.field.notnumber")
						.arg(obay)
						.arg("Please specify a valid number for " + field.getName());
			}
			// Length check
			if(element != null && element.getLength() != null && (element.getTextareaElement() != null ||
					element.getInputElement() != null && (element.getInputElement().getDatatype().equals("String") || element.getInputElement().getDatatype().equals("Number")))){
				unused = false;
				JArray obay2 = JExpr.newArray(codeModel.ref(Object.class)).add(JExpr.ref("messageSource").invoke("getMessage")
						.arg("label." + entity.getAlias() + ".field." + field.getName())
						.arg(JExpr.newArray(codeModel.ref(Object.class)))
						.arg(codeModel.ref(LocaleContextHolder.class).staticInvoke("getLocale")));
				if(!element.getLenmatch()){
					block = body._if(JExpr.ref("entity").invoke(getMethod).ne(JExpr._null())
								.cand(codeModel.ref(String.class).staticInvoke("valueOf").arg(JExpr.ref("entity").invoke("get" + StringUtils.capitalize(field.getName()))).invoke("length").gt(JExpr.lit(element.getLength()))))
							._then();
					invocation = block.invoke(JExpr.ref("errors"), "rejectValue");
					invocation.arg(field.getName())
							.arg("error.common.field.length.greater")
							.arg(obay2.add(JExpr.lit(element.getLength())))
							.arg("Please specify a valid value for " + field.getName() + ", not greater than " + element.getLength());
				}else{
					block = body._if(codeModel.ref(StringUtils.class).staticInvoke("isNotBlank").arg(JExpr.ref("entity").invoke(getMethod))
								.cand(JExpr.ref("entity").invoke(getMethod).invoke("trim").invoke("length").eq(JExpr.lit(element.getLength())).not()))
							._then();
					invocation = block.invoke(JExpr.ref("errors"), "rejectValue");
					invocation.arg(field.getName())
							.arg("error.common.field.length.noequal")
							.arg(obay2.add(JExpr.lit(element.getLength())))
							.arg("Please specify a valid value for " + field.getName() + ", equals to " + element.getLength());
				}
			}
		}
		if(unused)
			method.annotate(SuppressWarnings.class).param("value", "unused");
		// Customized Struct
		if(entity.getCustomized() != null && entity.getCustomized().indexOf("validator") != -1){
			body.directStatement("// Customized validator");
			String clazz = packageCustomized + ".Customized" + entity.getName() + "Validator";
			JType type = codeModel._ref(Class.forName(clazz));
			jc.field(JMod.PRIVATE, type, "customizedValidator").annotate(Autowired.class);
			body.invoke(JExpr.ref("customizedValidator"), "validate").arg(JExpr.ref("o")).arg(JExpr.ref("errors"));
		}
		
		codeModel.build(new File("src/main/java"));
	}

}
